#!/usr/local/apps/python/bin/python3.6
# -*- coding: utf-8 -*-


import smtplib
import time
from email.mime.text import MIMEText
from lib.logger import logger
import numpy
import influxdb

"""
common function:KDJ, MACD, EMA, MAn, 方差
"""


def ma_p(data=list(), key=None, period=5):
    """
    calculate moving average
    or named moving expectation
    :param data: list
    :param key: str data's element should be dict and contain key
    :param period: default 5, using n when n<=period, if period = 0,then caculate AA
    :return: list
    """
    start_time = time.time()
    # check parameters
    if not isinstance(data, list) or not isinstance(period, int) or period < 0:
        logger.error("data should be a list but get:{data_type},"
                     "period should be an unsigned integer but get {period_type} ({period})"
                     .format(data_type=type(data), period_type=type(period), period=period))
        return data
    if data.__len__() == 0:
        logger.error("data should not be empty")
        return data
    if not isinstance(data[0], dict):
        msg = "element in list should be dict but get first element type={},value={}".format(type(data[0]), data[0])
        logger.error(msg)
        return data
    if key is None or str(key) not in data[0].keys():
        logger.error("not found {key} in {item}".format(key=key, item=data[0]))
        return data

    # calculate
    result = []
    length = data.__len__()
    if period == 0:
        for i in range(length):
            item = data[i]
            item["AA"] = sum([x[key] for x in data[0:i+1]])/(i+1)
            result.append(item)
    else:
        for i in range(length):
            item = data[i]
            item["MA{}".format(period)] = \
                sum([x[key] for x in data[0 if i < period else i-period+1:i+1]])/(i+1 if i < period else period)
            result.append(item)
    logger.debug("function using {} s".format(time.time()-start_time))
    return result


def cal_variance(data, key, period, _type="ALL"):
    """
    计算移动方差
    方差计算： σ^2 = (∑(x-μ)^2)/N
    :param data: list 原始数据
    :param key: str 要计算的关键字
    :param period: int 周期
    :param _type: str 类型 ALL计算所有 否则只计算最后一个
    :return: list
    """

    length = data.__len__()
    # check parameters
    if not isinstance(data, list) or not isinstance(period, int) or period <= 0:
        logger.error("data should be a list but get:{data_type}"
                     "period should be an unsigned integer but get {period_type} ({period})"
                     .format(data_type=type(data), period_type=type(period), period=period))
        return data
    if length == 0:
        logger.error("data should not be empty")
        return data
    if str(key) not in data[0].keys():
        logger.error("not found {key} in {item}".format(key=key, item=data[0]))
        return data

    data = ma_p(data=data, key=key, period=period)

    # calculate
    result = []
    length = data.__len__()
    for i in range(length):
        item = data[i]
        item["variance_{}".format(period)] = sum([(x[key]-x["MA{}".format(period)])**2
                                                  for x in data[0 if i < period else i-period+1:i+1]]) / \
                                                (i+1 if i < period else period)
        result.append(item)

    return result


def ema(data=list(), key="close", period=26):
    """
    prepared for calculating macd
    ema：Exponential Moving Average
    :param data: list stock data of day K
    :param key: str which field to be calculated
    :param period: int period
    :return: list
    """
    start_time = time.time()
    result = []
    length = data.__len__()
    data[0]["ema%s" % period] = data[0][key]
    result.append(data[0])
    for i in range(1, length):
        item = data[i]
        item["ema%s" % period] = ((period-1)*data[i-1]["ema%s" % period]+2*item[key])/(period+1)
        result.append(item)
    logger.debug("function using {} s".format(time.time()-start_time))
    return result


def macd(data=list(), key="close"):
    """
    calculate macd
    MACD(n)=2(DIF(n)-DEA(n))
    DEA(n)=(8DEA(n-1) +2DIF(n))/10
    DIF(n)=EMA(12,n)-EMA(26,n)
    EMA(12,n) = (11*EMA(12,n-1) + 2*C(n))/13
    EMA(26,n) = (25*EMA(26,n-1) + 2*C(n))/26

    :rtype: object
    :param data: list stock data of day K
    :param key: str which field to be calculatd
    :return:
    """
    start_time = time.time()
    result = []
    length = data.__len__()
    data = ema(data=data, key=key, period=12)
    data = ema(data=data, key=key, period=26)
    data[0]["dif"] = float(data[0]["ema12"]-data[0]["ema26"])
    data[0]["dea"] = float(data[0]["dif"])
    data[0]["macd"] = float(2*(data[0]["dif"]-data[0]["dea"]))
    result.append(data[0])

    for i in range(1, length):
        item = data[i]
        item["dif"] = item["ema12"]-item["ema26"]
        item["dea"] = ((9-1)*data[i-1]["dea"]+2*item["dif"])/(9+1)
        item["macd"] = 2*(item["dif"]-item["dea"])
        result.append(item)
    logger.debug("function using {} s".format(time.time()-start_time))
    return result


def kdj(data=list(), period=9):
    """
    just for stock data
    :param data: list
    :param period int default 9
    :return: list
    """
    start_time = time.time()
    result = list()
    if data is None or len(data) == 0:
        logger.error("data is None or length is 0")
        return data
    length = data.__len__()
    temp = data
    for i in range(length):
        item = temp[i]
        if i == 0:
            item["rsv"] = float(100)
            item["kdj_k"] = float(100)
            item["kdj_d"] = float(100)
            item["kdj_j"] = float(100)
            result.append(item)
            continue
        min_low_n = min([x["low"] for x in temp[0 if i < period else i - period + 1:i+1]])
        max_high_n = max([x["high"] for x in temp[0 if i < period else i - period + 1:i+1]])
        try:
            item["rsv"] = 100*(item["close"]-min_low_n)/(max_high_n-min_low_n)
        except ZeroDivisionError as e:
            logger.error("{} {}".format(e, data))
            return None

        item["kdj_k"] = 2*temp[i-1]["kdj_k"]/3 + item["rsv"]/3
        item["kdj_d"] = 2*temp[i-1]["kdj_d"]/3 + item["kdj_k"]/3
        item["kdj_j"] = 3*item["kdj_k"] - 2*item["kdj_d"]
        result.append(item)
    logger.debug("function using {} s".format(time.time()-start_time))
    return result


def current_timestamp():
    """

    :return:
    """
    from time import strftime, localtime
    return strftime("%Y-%m-%d %H:%M:%S", localtime())


def send_mail(address_list=list(), msg="", subject="Title", _from="From:", to="To:"):
    """

    :param address_list: str email address
    :param msg: str message
    :param _from: str
    :param subject: str title
    :param to: str
    :return: boolean True or False
    """
    from_addr = 'sciengeek@earnbillion.cn'
    password = 'Sciengeek7'
    to_addr = address_list[0]

    smtp_server = 'smtp.mxhichina.com'
    message = MIMEText(msg, 'plain', 'utf-8')
    message['From'] = "{}".format(from_addr)
    message['To'] = ",".join(address_list)
    message['Subject'] = subject
    try:
        server = smtplib.SMTP_SSL(smtp_server, 465)
        server.login(from_addr, password)
        server.sendmail(from_addr, to_addr, message.as_string())
        server.quit()
        return True
    except smtplib.SMTPException as e:
        logger.error("send message failed: {}".format(e))
        return False
    except TimeoutError as e:
        logger.error("send message failed: {}".format(e))
        return False


def get_variance(data=list()):
    """
    get the expectation and standard deviation of the data(type:list)
    :return: dict expectation and standard deviation
    """
    if not data:
        return None

    average = sum(data)/(1.0 * data.__len__())
    variance = sum([(x-average)**2 for x in data])/(1.0 * data.__len__())
    return {
        "average": average,
        "standard_deviation": variance**0.5
    }


def get_positron_str(stock_data=None, key=None):
    """
    展现股票增长率的涨跌趋势，将其规整到01
    :param stock_data: list stock days data
    :param key: the key in dict which is an element of stock_data
    :return: string with 0 or 1, 0 means fall and 1 means rise
    """
    if stock_data is None:
        logger.warn("stock_data should not be empty")
        return ""
    if not isinstance(stock_data[0], dict):
        stock_data = [{"key": x} for x in stock_data]
        key = "key"
    if key not in stock_data[0].keys():
        logger.warn("not found {} inf stock_data[0]:{}".format(key, stock_data[0]))
        return ""
    result = ""
    length = stock_data.__len__()
    for i in range(1, length, 1):
        result += "0" if stock_data[i][key] < stock_data[i-1][key] else "-" \
            if stock_data[i][key] == stock_data[i-1][key] else "1"
    return result


def lwlr(point, x_arr, y_arr, k=1.0):
    """
    局部加权线性回归 根据给定点,一句局部加权线性回归算法，预估对应y
    :param test_point: 进行估计的点
    :param x_arr: 训练集X
    :param y_arr: 训练集Y
    :param k: 权重系数
    :return: 回归计算的结果
    """
    x_mat = numpy.mat(x_arr)  # 将数组转换为一个矩阵
    y_mat = numpy.mat(y_arr).T
    m = numpy.shape(x_mat)[0]  # 计算相应的对角矩阵
    weights = numpy.mat(numpy.eye(m))  # 初始化权重, 单位矩阵
    for i in range(m):
        dif_mat = point - x_mat[i, :]
        weights[i, i] = numpy.exp((dif_mat * dif_mat.T)/(-2.0*k**2))
    xTx = x_mat.T * (weights * x_mat)
    if numpy.linalg.det(xTx) == 0.0:
        logger.error("This matrix is singular, cannot do inverse")
        return None
    ws = xTx.I * (x_mat.T * (weights * y_mat))
    return point * ws


def get_all_lwlr_point(points, x_arr, y_arr, k=1.0):
    """
    针对数据集中每一个点 调用lwlr
    :param points: list  点集
    :param x_arr:
    :param y_arr:
    :param k: 权重
    :return:
    """
    m = numpy.shape(points)[0]
    y_hat = numpy.zeros(m)  # 初始化预测结果集
    for i in range(m):
        y_hat[i] = lwlr(points[i], x_arr, y_arr, k)
    return y_hat


def write_to_influxdb(host="127.0.0.1", points=list()):
    """
    将点的列表写到influxdb数据库
    :param host: str
    :param points: list 数据列表
    :return:
    """
    client = influxdb.InfluxDBClient(host, 8086, 'turing', 'turing', 'stock_d')
    client.write_points(points=points)
    client.close()


def generate_point(measurement="test", tags={}, item=dict(), timestamp=time.time()):
    """

    :param measurement: str 指标名
    :param tags: dict 标签列表
    :param item: 数据集
    :param timestamp: 时间戳 默认当前时间
    :return:
    """
    point = {
        "measurement": measurement,
        "tags": tags,
        "time": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(timestamp)),
        "fields": item,
    }
    return point


# -----------------------------------------------------------------------------
# 以下部分来自于网站:
# https://grisha.org/blog/2016/02/17/triple-exponential-smoothing-forecasting-part-iii/
# 三次指数平滑

def average(series):
    """
    计算序列series的均值
    :param series: list<float> 点集
    :return: float 序列series的均值
    """
    return float(sum(series))/len(series)


def moving_average(series, n):
    """
    计算移动均值
    :param series: list<float> 序列
    :param n: int 移动长度
    :return: 周期n的均值
    """
    return average(series[-n:])


def weighted_average(series, weights):
    """
    加权移动均值
    :param series: list<float>
    :param weights: list<float> 权重
    :return: list<float>
    """
    result = 0.0
    weights.reverse()
    for n in range(len(weights)):
        result += series[-n-1] * weights[n]
    return result


# given a series and alpha, return series of smoothed points
def exponential_smoothing(series, alpha):
    """
    给定一个序列和系数alpha， 返回该序列的一阶指数平滑点集
    :param series: list<float>  序列点
    :param alpha: float 系数
    :return: list<float>
    """
    result = [series[0]]  # 第一个点不做平滑预测直接加入
    for n in range(1, len(series)):
        result.append(alpha * series[n] + (1 - alpha) * result[n-1])
    return result


def double_exponential_smoothing(series, alpha, beta):
    """
    given a series and alpha, return series of smoothed points
    公式见这个网站:https://grisha.org/blog/2016/02/16/triple-exponential-smoothing-forecasting-part-ii/
    二次指数平滑, 对于第一个数不做平滑，并设置level=x0  trend=x1-x0
    相对于一次指数平滑, 二次平滑能够预测2个点
    :param series: list<float> 序列点
    :param alpha: 系数1
    :param beta: 系数2
    :return: list<float> 平滑预测点
    """
    # 第一个点不做平滑, 直接加入
    result = [series[0]]
    for n in range(1, len(series)+1):
        if n == 1:
            level, trend = series[0], series[1] - series[0]
        if n >= len(series): # we are forecasting
          value = result[-1]
        else:
          value = series[n]
        last_level, level = level, alpha*value + (1-alpha)*(level+trend)
        trend = beta*(level-last_level) + (1-beta)*trend
        result.append(level+trend)
    return result


def initial_trend(series, slen):
    """

    :param series:
    :param slen:
    :return:
    """
    sum = 0.0
    for i in range(slen):
        sum += float(series[i+slen] - series[i]) / slen
    return sum / slen


def initial_seasonal_components(series, slen):
    """
    三次指数平滑依赖函数
    :param series:
    :param slen:
    :return:
    """
    seasonals = {}
    season_averages = []
    n_seasons = int(len(series)/slen)
    # compute season averages
    for j in range(n_seasons):
        season_averages.append(sum(series[slen*j:slen*j+slen])/float(slen))
    # compute initial values
    for i in range(slen):
        sum_of_vals_over_avg = 0.0
        for j in range(n_seasons):
            sum_of_vals_over_avg += series[slen*j+i]-season_averages[j]
        seasonals[i] = sum_of_vals_over_avg/n_seasons
    return seasonals


def triple_exponential_smoothing(series, slen, alpha, beta, gamma, n_preds):
    """
    三次指数平滑
    :param series: list<float> 点集
    :param slen: int 周期长度
    :param alpha: float a
    :param beta: float b
    :param gamma: float r
    :param n_preds: int 要预测多少个点
    :return:
    """
    result = []
    seasonals = initial_seasonal_components(series, slen)
    for i in range(len(series)+n_preds):
        if i == 0:  # initial values
            smooth = series[0]
            trend = initial_trend(series, slen)
            result.append(series[0])
            continue
        if i >= len(series): # we are forecasting
            m = i - len(series) + 1
            result.append((smooth + m*trend) + seasonals[i%slen])
        else:
            val = series[i]
            last_smooth, smooth = smooth, alpha*(val-seasonals[i%slen]) + (1-alpha)*(smooth+trend)
            trend = beta * (smooth-last_smooth) + (1-beta)*trend
            seasonals[i%slen] = gamma*(val-smooth) + (1-gamma)*seasonals[i%slen]
            result.append(smooth+trend+seasonals[i%slen])
    return result


